C#进阶(下)

反射

程序集

程序集是由编译器编译得到的,供进一步编译执行的中间产物,在WINDOW系统中,它一般表现为后缀为.dil(库文件)或者是.exe(可执行文件)的格式。
简单来说就是我们写的代码集合,我们写的所有代码都会被翻译器翻译为一个代码集供人使用,比如一个代码库或者一个可执行文件。

元数据

元数据就是描述数据的数据
这个概念不仅仅用于程序上,在别的领域也有使用。
简单来说,程序中的类,类中的函数,函数中的变量就是元数据,有关程序以及类型的数据被称为元数据。它们保存在程序集中

反射

程序正在运行时,可以查看其他程序集或者自身的元数据,一个运行的程序产肯本身或者其他程序的元数据行为就是反射。
简单来说就是,在程序运行时,通过反射可以得到其他程序集和或者自己程序集代码的各种信息,实例化它们,执行它们,操作它们。

反射的作用

因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性。

  • 程序运行时得到所有元数据,包括元数据的特征
  • 程序运行时,实例化对象,操作对象
  • 程序运行时创建新对象,用这些对象执行任务。

语法

Type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Test
{
private int i = 1;
public int j = 0;
public string str = "宇智波"
public Test()
{

}
public Test(int i)
{
this.i = i;
}
public Test(int i, string str):this(i)
{
this.str = str;
}
public void Speak()
{
Console.WriteLine("speak")
}
}

Type是类的信息类,是反射功能的基础,它是访问元数据的主要方式,使用Type的成员获取有关类型声明的信息,有关类型的成员

获取Type

万物之父中的GetType()可以获取对象的Type

1
2
3
int a = 42;
Type type = a.GetType();
Console.WriteLine(type);//打印出来的是int的所有信息。

通过Typeof来找到Type

1
2
Type type2 = typeof(int);
Console.WriteLine(type);//打印出来的是int的所有信息。

通过类名字获取Type

1
2
Type type = Type.GetType("System.Int32");
Console.WriteLine(type);//打印出来的是int的所有信息。

得到类的程序集信息

1
Console.WriteLine(type.Assembly);

获取类中的所有公共成员

1
2
3
4
5
6
7
using System.Reflection;
Type t = typeof(Test);
MemberInfo[] infos = t.GetMembers();
for(int i = 0; i < infos.Length; i++)
{
Console.WriteLine(infos[i]);
}

获取类的公共构造函数并调用

1
2
3
4
5
ConstructorInfo[] ctors = t.GetConstructors();
for(int i = 0; i < ctors.Length; i++)
{
Console.WriteLine(ctors[i]);
}
1
2
3
4
5
6
7
8
9
10
11
//获取无参构造函数并进行无参构造
ConstructInfo info = t.GetConstructor(new Type[0]);
Test obj = info.Invoke(null) as Test;

//获取有参构造
ConstructorInfo info2 = t.GetConstructoe(new Type[]{typeof(int)});
obj = info2.Invoke(new object[] {2}) as Test;
//两个参数的有参构造
ConstructorInfo info3 = t.GetConstructor(new Type[]{typeof(int), typeof(string)});
obj = info3.Invoke(new object[] {2, "宇智波"}) as Test;
//这里是用obj来承载我们获取的函数

获取类的公共成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//得到所有成员变量
FieldInfo[] field = t.GetFields();
for(int i = 0; i < field.Length; i++)
{
Console.WriteLine(fieldInfo[i]);
}
//得到指定名称的公共成员变量
FieldInfo info1 = t.GetField("j");
Console.WriteLine(info1);
//通过反射获取和设置对象的值
Test test = new Test;
test.j = 114514;
test.str = "宇智波";
//通过反射获取队形的某个变量的值
Console.WriteLine(info1.GetValue(test));
//设定某个对象的某个变量的值
info1.SetValue(test, 100);
Console.WriteLine(info1.GetValue(test));
//获得类的公共成员方法
Type strType = typeof(string);
MethodInfo[] methods = strType.GetMethods();
for(int i = 0; i < methods.Length; i++)
{
Console.WriteLine(methods[i]);
}
//此处的到substring方法
MethodInfo[] substr = strType.GetMethods("Substring", new Type[]{typeof(int), typeof(int)});
//调用
string str = "宇智波带土";
//第一个参数是哪个对象要执行这个方法
object res = substr.Invoke(str, new object[] {0, 3});
Console.WriteLine(res);

Activator

用于快速实例化对象的类,用于将Type对象快捷实例化为对象,先得到Type然后快速实例化一个对象。

无参构造

1
2
3
4
Type test = typeof(Test);
//默认调用无参构造函数
Test t = Activator.CreatInstance(test) as Test;
Console.WriteLine(t.str);

有参构造

1
2
t = Activator.CreatInstance(test, 100) as Test;//此处type之后是一个变长参数,有多少填多少对应的值即可
Console.WriteLine(t.j);

Assembly

程序集类
主要是用来加载其他程序集,加载后才能用Type来使用其他程序集中的信息,如果想要使用不是自己程序集中的内容,需要先加载程序集,比如dil文件。
简单来说dil就是把库文件开成一种代码仓库,它提供给使用者一些可以直接拿来用的变量,函数或者类。

三种加载程序集的函数

1
2
3
4
5
6
7
8
//加载一个指定程序集
Assembly assembly = Assembly.LoadFrom("目标程序集(dil文件)路径");
Type[] types = assembly.GetTypes();
for(int i = 0; i < types.Length; i++)
{
Console.WriteLine(types[i]);
//查看引用的程序集有哪些类
}
1
2
3
4
5
6
7
8
9
//加载其中的类对象,然后使用反射。
Type icon = assembly.GetType("填完整的命名空间.类名");
Type move = assembly.GetType("命名空间.枚举");
FieldInfo right = move.GetField("Right");
//实例化对象
object obj = Activator.CreatInstance(icon,10,5,right.GetValue(null));
//得到对象中的方法
MethodInfo move = icon.GetMethod("Move");
MethodInfo draw = icon.GetMethod("Draw");

类库工程的创建

直接创建。

特性

特性是什么

特性是一种允许我们向程序集添加元数据的语言结构,它是用于保存程序结构信息的某种特殊类型的类。

特性提供功能强大的方式以将声明信息和c#代码相关联,特性与程序实体关联后,即可在运行时使用反射查询特性信息。

特性的目的是告诉我们编译器把程序结构的某组元数据放入程序集中,它可以放置在几乎所有的声明中

简单来说,特性的本质是一个类,我们可以利用特性类为元数据添加额外信息,之后通过反射来获取这些额外信息。

自定义特性

继承基类Attribute

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyAttribute : Attribute
{
//特性中的成员,一般根据需求来写
public string str;
public MyAttribute(string str)
{
this.str = str;
}
public void MyFun()
{
Console.WriteLine("特性的方法");
}
}

特性的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[My("这是一个类")]//此处会省略掉Attribute
class MyClass
{
[My("这是一个变量")]
public int value;
[My("这是一个函数")]//这里是调用了上面的构造函数,因此需要传入一个字符串。
public void Test([My("这是一个函数参数")]int a)
{

}
}

MyClass mc = new MyClass();
Type t = mc.GetType();
//t = Typeof(MyClass);
//判断是否使用了某个特性
//参数一表示参数的类型,参数二代表是否继承链(属性和事件忽略此参数)
if(t.IsDefined(typeof(MyAttribute), false))
{
Console.WriteLine("应用了该特性")
}
//获取Type元数据中的所有特性
object[] array = t.GetCustomAttribute(true);
for(int i = 0; i < array.Length; i++)
{
if(array[i] is MyAttribute)
{
Console.WriteLine((array[i] as MyAttribute).info);
(array[i] as MyAttribute).MyFun;
}
}

限制自定义特性的使用范围

通过为特性类加特性限制其使用范围。
参数一:AttributeTargets表示能够用在哪些地方,例如这里指能用在class和struct上
参数二:AllowMultiple是否允许多个特性被用在同一个目标上,例如这里true表示可以
参数三:Inherited表示是否能被派生类和重写成员继承。

1
2
3
4
5
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
public class My2Attribute : Attributs
{

}

系统自带特性——过时特性

Obsolete,用于提示用户使用的方法等成员已经过时,建议使用新方法,一般加在函数前
参数一:调用过时方法是提示的内容
参数二:如果为true则在编译时会报错,如果为false则会警告。

1
2
3
4
5
6
7
8
9
10
11
12
class Test
{
[Obsolete("方法已过时,请使用新方法", false)]
public void Old(string str)
{

}
public void Speak
{

}
}

系统自带特性——调用者信息特性

CallerFilePath——哪个文件调用
CallerLineNumber——哪一行调用
CallerMemberName——哪个函数调用
需要引用命名空间 using System.Runtime.CompilerServices;

1
2
3
4
5
6
7
8
9
10
class Test
{
public void speak(string str, [CallerFilePath]string fileName = "", [CallerLineNumber]int line = 0, [CallerMemberName]string target = "")
{
Console.WriteLine(str);
Console.WriteLine(fileName);
Console.WriteLine(line);
Console.WriteLine(target);
}
}

系统自带特性——条件编译特性

Conditional,它会和预处理指令#define配合使用
需要引用命名空间using System.Diagnostics;
主要可以用在一些调试代码上,有时想执行有时不想执行的代码

1
2
3
4
5
6
7
8
[Conditional("Fun")]
static void Fun()
{
Console.WriteLine("Fun执行")
}
Fun();//此时不会打印信息
#define Fun
Fun();//加上define才会打印

系统自带特性——外部Dll包函数特性

DllImport,用来标记非.Net的函数,表明该函数在一个外部的DLL中定义,一般用来调用c或者c++的Dll包好的方法。
需要引用命名空间using System.InteropServices

1
2
[DllImport("Test.dll")]//括号里写的路径或者是dll包
public static int Add(int a, int b);

迭代器

迭代器是什么

迭代器,有时又称光标,是程序设计的软件设计模式,迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素,二又不暴露其内部的标识。
从表现效果上看,是可以在容器对象上遍历访问的接口,设计人员无需关心容器对象的内存分配的实现细节,可以用foreach遍历的类,都是实现了迭代器的

标准迭代器的实现方法

关键接口:IEnumerator,IEnumerable
命名空间:using System.Collections;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class List : IEnumerable//继承接口
{
private int[] list;
public List()
{
list = new int[]{1, 2, 3, 4, 5, 6, 7, 8};
}
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}//调用方法
}
//foreach本质会先调用GetEnumerable方法来获取
foreach(int item in list)
{

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class List : IEnumerable, IEnumerator//继承接口
{
private int[] list;
public int position = -1;
public List()
{
list = new int[]{1, 2, 3, 4, 5, 6, 7, 8};
}
public IEnumerator GetEnumerator()
{
Reset();
return this;
}//调用方法
public object Current
{
get
{
return list[position];
}
}
public bool MoveNext()
{
++position;
return position < list.Length;
}
public void Reset()//用于重置光标位置
{
position = -1;
}
}
//foreach本质会先调用GetEnumerable方法来获取
foreach(int item in list)
{

}

yield return语法糖实现迭代器

c#提供给我们的语法糖,将复杂的逻辑简单化,可以增加程序实现的可读性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Cus2 : IEnumerable
{
private int[] list;
public Cus2()
{
list = new int[]{1, 2, 3, 4, 5, 6, 7, 8};
}
public IEnumerator GetEnumerator()
{
for(int i = 0; i < list.Length; i++)
{//yield关键字都是配合迭代器使用的,可以理解为暂时返回,保留当前状态
yield return list[i];
}
}
}

yield return语法糖为泛型类实现迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Cus2<T> : IEnumerable
{
private T[] list;
public Cus2(params T[] list)
{
this.list = list;
}
public IEnumerator GetEnumerator()
{
for(int i = 0; i < list.Length; i++)
{//yield关键字都是配合迭代器使用的,可以理解为暂时返回,保留当前状态
yield return list[i];
}
}
}

特殊语法

var隐式类型

var是一种特殊的变量类型,它可以用来表示任意类型的变量
注意:var不能作为类成员,只能用于临时变量声明时,一般写在函数语句块中,var必须初始化

设置对象初始值

声明对象时可以通过直接写大括号的形式初始化公共成员变量和属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person
{
private int money;
public bool sex;
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
public Person(int money)
{

}
}
Person p = new Person(){sex = true, Age = 18, Name = "宇智波带土"};

设置集合的初始值

声明集合对象时,也可以通过大括号直接初始化。

1
2
3
4
5
6
7
8
int[] array = new int[] {1, 2, 3, 4, 5};
List<int> listint = new List<int>() {1, 2, 3, 4, 5};
List<Person> = new List<Person>() {new Person(100), new Person(200){Age = 10}, new Person(300){Name = "niuyeye"}};
Dictionary<int, string> dic = new Dictionary<int, string>()
{
{1, "123"};
{2, "222"};
}

匿名类型

var变量可以申明为自定义的匿名类型

1
2
3
4
var v = new {age = 18, money = 100, name = "小明"};
Console.WriteLine(v.age);
Console.WriteLine(v.money);
Console.WriteLine(v.name);

可空类型

1、值类型不能赋值为空
2、申明是在值类型后面加一个?就可赋值为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int? c = null;

//判断是否为空
if(c.HasValue)
{
Console.WriteLine(c);
Console.WriteLine(c.Value);
}
//安全获取可空类型
int? value = null;
//如果为空,则默认返回为值类型的默认值
Console.WriteLine(value.GetValueOrDefault());
//也可以指定返回一个默认值
Console.WriteLine(value.GetValueOrDefault(100));

空合并操作符

左边值 ??右边值,
如果左边值为null就返回右边值,否则返回左边值
只要是可以为null的类型都能用

1
2
3
4
int? intV = null;
int intI = intV ?? 100;
string? str = null;
string str1 = str ?? "空";

内插字符串

$ ,用$来构造字符串,让字符串可以拼接变量。

1
2
string name = "宇智波";
Console.WriteLine($"邪恶的,{name}");

单句逻辑简略写法

1
2
3
if(true)
Console.WriteLine();
public int Add(int x, int y) => x + y;

值类型和引用类型2

如何判断值类型和引用类型

f12查看内部,如果是struct就是值类型,是class就是引用类型

语句块

命名空间——类,接口,结构体——函数,属性,索引器——条件分支,循环

变量的生命周期

编程时,大部分都是临时变量,语句块执行结束时,没有被记录的对象将被回收变成垃圾,值类型:被系统自动回收,引用类型:栈上用于存地址的房间自动回收,对重具体内容变为垃圾。

想要不会被回收或不变成垃圾,必须将其记录下来,一般会在更高层记录或者只用静态全局变量

结构体中的值和引用

结构体本身就是值类型,结构体中的值,栈中存储值具体的内容,结构体中的引用,堆中存储引用具体的内容。
引用类型始终存储在堆中,真正通过结构体使用其中引用类型时只是顺藤摸瓜。

类中的值和引用

类本身就是引用类型,在类中的值,堆中存储具体的值,在类中的引用,堆中存储具体的值

数组中的存储规则

与类一同,因为都是引用类型

结构体继承接口

利用里式替换原则,用接口容器装在结构体存在装箱拆箱,